/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.objer.objectid;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.security.MessageDigest;

import org.zhiqim.git.GitConstants;

import org.zhiqim.kernel.constants.EnumConstants;
import org.zhiqim.kernel.enumerated.LetterCase;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.Hexs;
import org.zhiqim.kernel.util.Ints;
import org.zhiqim.kernel.util.Validates;
import org.zhiqim.kernel.util.codes.SHA1;

/**
 * 对象编号基类
 *
 * @version v1.0.0 @author zouzhigang 2017-3-27 新建与整理
 */
public class ObjectId implements Comparable<ObjectId>, GitConstants, EnumConstants
{
    protected int w1;
    protected int w2;
    protected int w3;
    protected int w4;
    protected int w5;
    
    public ObjectId(int w1, int w2, int w3, int w4, int w5)
    {
        this.w1 = w1;
        this.w2 = w2;
        this.w3 = w3;
        this.w4 = w4;
        this.w5 = w5;
    }

    protected ObjectId(ObjectId src)
    {
        this.w1 = src.w1;
        this.w2 = src.w2;
        this.w3 = src.w3;
        this.w4 = src.w4;
        this.w5 = src.w5;
    }
    
    public int compareTo(ObjectId other)
    {
        if (this == other)
            return 0;

        int cmp = Ints.compareUnsigned(w1, other.w1);
        if (cmp != 0)
            return cmp;

        cmp = Ints.compareUnsigned(w2, other.w2);
        if (cmp != 0)
            return cmp;

        cmp = Ints.compareUnsigned(w3, other.w3);
        if (cmp != 0)
            return cmp;

        cmp = Ints.compareUnsigned(w4, other.w4);
        if (cmp != 0)
            return cmp;

        return Ints.compareUnsigned(w5, other.w5);
    }
    
    /*************************************************************************************/
    //获取属性方法
    /*************************************************************************************/
    
    /** 用于包索引文件的fanout对应对象编号第一个字节，如0x01 */
    public final int getFirstByte()
    {
        return w1 >>> 24;
    }

    /** 获取SHA-1小写字符串 */
    public final String getName()
    {
        return new String(toHexCharArray());
    }
    
    /** 获取短名，默认前7位 */
    public final String getShortName()
    {
        return getName().substring(0, 7);
    }

    /** 是否起始于简短对象编号 */
    public boolean startsWith(String shortName)
    {
        return getName().startsWith(shortName);
    }
    

    /*************************************************************************************/
    //拷贝到buffer&stream
    /*************************************************************************************/

    /** 拷贝十六进制字符到输出流 */
    public void copyTo(OutputStream out) throws IOException
    {
        out.write(toHexByteArray());
    }

    /** 拷贝十六进制字符到输出字符流 */
    public void copyTo(Writer writer) throws IOException
    {
        writer.write(toHexCharArray(), 0, LEN_OBJECT_ID_STRING);
    }

    /** 拷贝十六进制字符到字符串缓冲中 */
    public void copyTo(StringBuilder strb)
    {
        strb.append(toHexCharArray(), 0, LEN_OBJECT_ID_STRING);
    }
    
    /** 拷贝原生值到缓存中 */
    public void copyRawTo(ByteBuffer buf)
    {
        buf.putInt(w1);
        buf.putInt(w2);
        buf.putInt(w3);
        buf.putInt(w4);
        buf.putInt(w5);
    }

    /** 拷贝原生值到缓存中 */
    public void copyRawTo(byte[] b, int off)
    {
        Bytes.BU.putInt(b, off, w1);
        Bytes.BU.putInt(b, off + 4, w2);
        Bytes.BU.putInt(b, off + 8, w3);
        Bytes.BU.putInt(b, off + 12, w4);
        Bytes.BU.putInt(b, off + 16, w5);
    }

    /** 拷贝原生值到缓存中 */
    public void copyRawTo(int[] b, int off)
    {
        b[off] = w1;
        b[off + 1] = w2;
        b[off + 2] = w3;
        b[off + 3] = w4;
        b[off + 4] = w5;
    }

    /** 拷贝原生值到输出流中 */
    public void copyRawTo(OutputStream out) throws IOException
    {
        byte[] b = new byte[LEN_OBJECT_ID];
        copyRawTo(b, 0);
        out.write(b);
    }
    
    /*************************************************************************************/
    //hashCode & equals & toString
    /*************************************************************************************/

    public int hashCode()
    {
        return w1;
    }

    public boolean equals(Object o)
    {
        return (o instanceof ObjectId)?equals(this, (ObjectId)o):false;
    }

    @Override
    public String toString()
    {
        return "ObjectId[" + getName() + "]";
    }

    public char[] toHexCharArray()
    {
        return Bytes.toCharArray(toHexByteArray());
    }
    
    private byte[] toHexByteArray()
    {
        byte[] buf = new byte[LEN_OBJECT_ID_STRING];
        
        Bytes.BU.putHexBytes(buf, 0, w1, LetterCase.LOWER);
        Bytes.BU.putHexBytes(buf, 8, w2, LetterCase.LOWER);
        Bytes.BU.putHexBytes(buf, 16, w3, LetterCase.LOWER);
        Bytes.BU.putHexBytes(buf, 24, w4, LetterCase.LOWER);
        Bytes.BU.putHexBytes(buf, 32, w5, LetterCase.LOWER);
        
        return buf;
    }
    
    /**************************************************************************************/
    //静态方法
    /**************************************************************************************/

    /** 判断是否是一个对象编号 40个HEX字符 */
    public static final boolean isObjectId(String oid)
    {
        return Validates.isLen(oid, 40, 40) && Validates.isHexString(oid);
    }

    /** 转化为十六进制字符串 */
    public static final String toString(final ObjectId oid)
    {
        return oid != null ? oid.getName() : ZEROID_STR;
    }
    
    
    /** 比较两个对象是否相等，两个对象不允许为null */
    public static boolean equals(ObjectId firstObjectId, ObjectId secondObjectId)
    {
        if (firstObjectId == secondObjectId)
            return true;

        //优先从w2开始判断，因为w1作为hashCode，只有w1相等才比较
        return firstObjectId.w2 == secondObjectId.w2 && 
                firstObjectId.w3 == secondObjectId.w3 && 
                firstObjectId.w4 == secondObjectId.w4 && 
                firstObjectId.w5 == secondObjectId.w5 && 
                firstObjectId.w1 == secondObjectId.w1;
    }
    
    /** 比较两个对象的字节数组是否相等 */
    public static boolean equals(byte[] firstBuffer, int firstOff, byte[] secondBuffer, int secondOff)
    {
        return firstBuffer[firstOff] == secondBuffer[secondOff] && 
                firstBuffer[firstOff + 1] == secondBuffer[secondOff + 1] && 
                firstBuffer[firstOff + 2] == secondBuffer[secondOff + 2] && 
                firstBuffer[firstOff + 3] == secondBuffer[secondOff + 3] && 
                firstBuffer[firstOff + 4] == secondBuffer[secondOff + 4] && 
                firstBuffer[firstOff + 5] == secondBuffer[secondOff + 5] && 
                firstBuffer[firstOff + 6] == secondBuffer[secondOff + 6] && 
                firstBuffer[firstOff + 7] == secondBuffer[secondOff + 7] && 
                firstBuffer[firstOff + 8] == secondBuffer[secondOff + 8] && 
                firstBuffer[firstOff + 9] == secondBuffer[secondOff + 9] && 
                firstBuffer[firstOff + 10] == secondBuffer[secondOff + 10] && 
                firstBuffer[firstOff + 11] == secondBuffer[secondOff + 11] && 
                firstBuffer[firstOff + 12] == secondBuffer[secondOff + 12] && 
                firstBuffer[firstOff + 13] == secondBuffer[secondOff + 13] && 
                firstBuffer[firstOff + 14] == secondBuffer[secondOff + 14] && 
                firstBuffer[firstOff + 15] == secondBuffer[secondOff + 15] && 
                firstBuffer[firstOff + 16] == secondBuffer[secondOff + 16] && 
                firstBuffer[firstOff + 17] == secondBuffer[secondOff + 17] && 
                firstBuffer[firstOff + 18] == secondBuffer[secondOff + 18] && 
                firstBuffer[firstOff + 19] == secondBuffer[secondOff + 19];
    }
    
    /**************************************************************************************/
    //静态方法，解析字节数组成对象编号
    /**************************************************************************************/
    
    /** 解析字节数组成对象编号 */
    public static final ObjectId parse(byte[] buf)
    {
        return parse(buf, 0);
    }

    /** 解析字节数组成对象编号，指定字节数组偏移值 */
    public static final ObjectId parse(byte[] buf, int off)
    {
        int a = Bytes.BU.getInt(buf, off);
        int b = Bytes.BU.getInt(buf, off + 4);
        int c = Bytes.BU.getInt(buf, off + 8);
        int d = Bytes.BU.getInt(buf, off + 12);
        int e = Bytes.BU.getInt(buf, off + 16);
        return new ObjectId(a, b, c, d, e);
    }
    
    /** 解析整型数组成对象编号 */
    public static final ObjectId parse(int[] buf)
    {
        return parse(buf, 0);
    }

    /** 解析整型数组成对象编号，指定数组偏移值 */
    public static final ObjectId parse(int[] buf, int off)
    {
        return new ObjectId(buf[off], buf[off + 1], buf[off + 2], buf[off + 3], buf[off + 4]);
    }

    /** 解析十六进制字符串成对象编号 */
    public static final ObjectId parse(String oid)
    {
        Asserts.assertNotNull(oid, "从字符串解析成对象编号时，字符串不能为null");
        Asserts.asserts(oid.length() == LEN_OBJECT_ID_STRING, "从字符串解析成对象编号时[" + oid + "]不是40位SHA-1值");

        return parse(Hexs.toBytes(oid), 0);
    }

    /** 解析十六进制字符串转化为字节数组成对象编号 */
    public static final ObjectId parseHex(byte[] buf, int off)
    {
        Asserts.assertNotNull(buf, "从十六进制字符串转化的字节数组解析成对象编号时，字节数组不能为null");
        Asserts.asserts(buf.length >= off + LEN_OBJECT_ID_STRING, "从十六进制字符串转化的字节数组解析成对象编号时，字节数组长度必须大于40");
        
        return parse(Hexs.toBytes(buf, off, LEN_OBJECT_ID_STRING), 0);
    }
    
    /**
     * 通过类型和内容，生成对象编号
     * 
     * @param type      对象类型
     * @param data      对象内容
     * @return          对象编号
     */
    public static ObjectId parseContent(int type, byte[] data)
    {
        MessageDigest md = SHA1.getInstance();
        md.update(RevObject.typeBytes(type));
        md.update((byte)_SPACE_);
        md.update(Bytes.toBytesASCII(data.length));
        md.update((byte)0);
        md.update(data);
        
        return ObjectId.parse(md.digest());
    }
}
